package MULE.Model.Logic;

import javax.swing.*;

import MULE.Model.Game;
import MULE.Model.Player;
import MULE.Model.SavedGame;
import MULE.Model.Store;
import MULE.Model.Panel.GamePanel;
import MULE.Model.Panel.GameStatusPanel;
import MULE.Model.Panel.MapPanel;
import MULE.Model.Panel.FooterPanel;
import MULE.Model.Panel.MenuPanel;
import MULE.Model.Panel.TitlePanel;
import MULE.Model.Panel.TownPanel;

import java.awt.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * Game driver as singleton. The game's components (model, view, presenter) are
 * initialized in this class. The Game object and UI views can be manipulated
 * through the MuleDriver.
 * 
 * @author jstep
 * 
 */
public class MuleDriver {

	// Window dimension constants.
	// public static final double GOLDEN_RATIO = 1.618;
	public static final int WINDOW_HEIGHT = 490;
	public static final int WINDOW_WIDTH = 576;
	public static final String SAVE_FILE_FORMAT = ".txt";

	private static JFrame windowFrame;
	private static Game game;
	private static GameLogic gameLogic;
	private static TownLogic townLogic;
	private static MapLogic mapLogic;
	private static EmplacementLogic emplacementLogic;
	private static GameStatusPanel statusPanel;
	private static FooterPanel footerPanel;
	private static PropertySelectionLogic propertySelectionLogic;

	/**
	 * Calls the game driver's initialization method. Program execution begins
	 * here.
	 * 
	 * @param args
	 *            Command-line arguments.
	 */
	public static void main(String[] args) {
		MuleDriver.initialize();
	}

	/**
	 * Returns a reference to the Mule game object.
	 * 
	 * @return Reference to Game object.
	 */
	public static Game getGame() {
		return MuleDriver.game;
	}

	/**
	 * Sets currently displayed view to given JPanel.
	 * 
	 * @param view
	 *            JPanel the application should display.
	 */
	public static void setView(JPanel view) {
		windowFrame.getContentPane().removeAll();
		windowFrame.getContentPane().add(view);
		windowFrame.validate();
		windowFrame.repaint();
	}

	/**
	 * Returns the dimensions of the game view's main content area. This is the
	 * area between the status and scoreboard panels.
	 * 
	 * @return Dimensions of the game view's main content area.
	 */
	public static Dimension getGameViewSize() {
		return ((GamePanel) gameLogic.getView()).getMainContentSize();
	}

	/**
	 * Sets the currently displayed main content of the GamePanel. The status
	 * and scoreboard panels are untouched.
	 * 
	 * @param view
	 *            The view to show in the main area of the game panel.
	 */
	public static void setGameView(JPanel view) {
		GamePanel gamePanel = (GamePanel) gameLogic.getView();
		gamePanel.setMainContent(view);
		gamePanel.assemble();
	}

	/**
	 * Initializes the application's game logic. The game logic is responsible
	 * for running through the game. This method should not be called until the
	 * game object has been initialized (after game configuration).
	 * 
	 */
	public static void initCoreLogic(Game g) {
		// Set the application's game object.
		game = g;
		// Create status, map, and scoreboard panels.
		statusPanel = new GameStatusPanel();
		MapPanel mapPanel = new MapPanel(game.getMap());
		mapLogic = new MapLogic(mapPanel);
		footerPanel = new FooterPanel(game.getPlayers());
		// Menu panels/logic.
		MenuPanel menuPanel = new MenuPanel();
		MenuLogic menuLogic = new MenuLogic(menuPanel);
		footerPanel.addMenuButtonListener(menuLogic);
		propertySelectionLogic = new PropertySelectionLogic(mapPanel);
		emplacementLogic = new EmplacementLogic(null);
		// Create game panel.
		GamePanel gamePanel = new GamePanel(statusPanel, mapLogic, footerPanel);
		GameLogic gLogic = new GameLogic(gamePanel, game);
		gameLogic = gLogic;
		// Create town panel.
		TownPanel townPanel = new TownPanel(gamePanel.getMainContentSize());
		townLogic = new TownLogic(townPanel);
	}

	/**
	 * Returns the application's game logic module.
	 * 
	 * @return The application's game logic module.
	 */
	public static GameLogic getGameLogic() {
		return gameLogic;
	}

	/**
	 * Returns the application's map logic module.
	 * 
	 * @return The application's map logic module.
	 */
	public static MapLogic getMapLogic() {
		return mapLogic;
	}

	/**
	 * Returns the application's town logic module.
	 * 
	 * @return The application's town logic module.
	 */
	public static TownLogic getTownLogic() {
		return townLogic;
	}

	/**
	 * Updates the scoreboard for the given player.
	 * 
	 * @param player
	 *            The player whose score should be updated.
	 */
	public static void updateScore(Player player) {
		footerPanel.updateScore(player);
	}

	/**
	 * Sets the application's dialogue text.
	 * 
	 * @param text
	 *            Text to show in dialogue.
	 */
	public static void setCaptionText(String text) {
		statusPanel.setText(text);
	}

	/**
	 * Tells the application to show a confirmation dialog window with given
	 * title and message. The confirmation dialog has a yes/no option.
	 * 
	 * @param title
	 *            The dialog window's title.
	 * @param message
	 *            The dialog window's message.
	 * @return True if the user clicks "Yes", false if the user clicks "No".
	 */
	public static boolean showConfirmDialog(String title, String message) {
		int n = JOptionPane.showConfirmDialog(windowFrame, message, title,
				JOptionPane.YES_NO_OPTION);
		return n == 0;
	}

	/**
	 * Tells the application to show an alert dialog window with given title and
	 * message.
	 * 
	 * @param title
	 *            The dialog window's title.
	 * @param message
	 *            The dialog window's message.
	 */
	public static void showAlertDialog(String title, String message) {
		JOptionPane.showMessageDialog(windowFrame, message, title,
				JOptionPane.WARNING_MESSAGE);
	}

	/**
	 * Shows a text input dialog with given title and message.
	 * 
	 * @param title
	 *            The title of the input dialog.
	 * @param message
	 *            The dialog's message.
	 * @return String input by user.
	 */
	public static String showInputDialog(String title, String message) {
		String input = (String) JOptionPane.showInputDialog(windowFrame,
				message, title, JOptionPane.PLAIN_MESSAGE);
		return input;
	}

	/**
	 * Initializes the game driver and shows the GUI.
	 */
	public static void initialize() {
		// Initialize main window frame.
		windowFrame = new JFrame("M.U.L.E.");
		windowFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		windowFrame
				.setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
		// Show window.
		windowFrame.setVisible(true);
		windowFrame.pack();
		// Show title panel.
		TitlePanel titlePanel = new TitlePanel();
		TitleLogic titleLogic = new TitleLogic(titlePanel);
		titleLogic.showView();
	}

	public static EmplacementLogic getEmplacementLogic() {
		return emplacementLogic;
	}

	public static PropertySelectionLogic getPropertySelectionLogic() {
		return propertySelectionLogic;
	}

	/**
	 * Saves the current game in a file with given name.
	 * 
	 * @param saveName
	 *            The saved game's name.
	 */
	public static boolean saveGame(String saveName) {
		try {
			// Open IO stream.
			String fileName = saveName + SAVE_FILE_FORMAT;
			FileOutputStream fileOut = new FileOutputStream(fileName);
			ObjectOutputStream out = new ObjectOutputStream(fileOut);
			// Create save game object.
			SavedGame saveGame = new SavedGame();
			saveGame.setGame(game);
			saveGame.setStorehouse(Store.getStorehouse());
			saveGame.setCurrentRound(gameLogic.getCurrentRound());
			saveGame.setCurrentPlayer(gameLogic.getCurrentPlayer());
			saveGame.setRemainingTime(gameLogic.getRemainingTurnTime());
			// Write object and close IO stream.
			out.writeObject(saveGame);
			out.close();
			fileOut.close();
			System.out.println("Serialized data is saved in " + fileName);
			return true;
		} catch (IOException i) {
			i.printStackTrace();
		}
		return false;
	}

	/**
	 * Loads a game of MULE from given file name.
	 * 
	 * @param fileName
	 *            Name of the file where game is saved.
	 */
	public static boolean loadGame(String fileName) {
		try {
			FileInputStream fileIn = new FileInputStream(fileName);
			ObjectInputStream in = new ObjectInputStream(fileIn);
			// Create saved game object from file.
			SavedGame saveGame = (SavedGame) in.readObject();
			// Restore game.
			initCoreLogic(saveGame.getGame());
			Store.setStorehouse(saveGame.getStorehouse());
			gameLogic.setCurrentRound(saveGame.getCurrentRound());
			gameLogic.setCurrentPlayer(saveGame.getCurrentPlayer());
			gameLogic.setRemainingTurnTime(saveGame.getRemainingTime());
			// Close IO stream.
			in.close();
			fileIn.close();
			return true;
		} catch (IOException i) {
			System.out.println("Load game: File not found");
		} catch (ClassNotFoundException c) {
			System.out.println("Load game: Class not found");
			c.printStackTrace();
		}
		return false;
	}

	/**
	 * Exits the game.
	 */
	public static void exitGame() {
		System.out.println("Quitting game...");
		// windowFrame.dispatchEvent(new WindowEvent(windowFrame,
		// WindowEvent.WINDOW_CLOSING));
		windowFrame.setVisible(false);
		windowFrame.dispose();
		System.exit(0);
	}
}
